/**
 * Interprocess communications module for PyS60
 *
 * __author__    = "Jussi Toivola <jussi@redinnovation.com>"
 * __copyright__ = "2008 Red Innovation Ltd."
 * __license__   = "AGPL"
 */

#include <e32msgqueue.h>

#include <Python.h>
#include <symbian_python_ext_util.h>


//#include "LogMan.h" //RLogMan

////////////////////////////////////////////////////////////////////

#define MsgQueue_TypeString "MsgQueue.type_MsgQueue"
#define MsgQueueTypeObject (*(PyTypeObject *)SPyGetGlobalString(MsgQueue_TypeString))

#define BUF_SIZE 128//RMsgQueueBase::KMaxLength - 1

typedef RMsgQueue<TBuf8<BUF_SIZE> > RMessageQueue;

/// Message for Python exception
const char* KErrMsgQueueNotOpen = "Queue has not been opened or created.";
/// Message for Symbian panic.
_LIT( KMsgInvalidNotifier, "Invalid notifier type" );

enum TNotifierType
{
    EDataNotifier,
    ESpaceNotifier
};

class CMyActiveObject : public CActive
{
public:

    ~CMyActiveObject()
    {
        Cancel();
        if ( iCallback )
        {
            Py_XDECREF( iCallback );
        }
    };

    void StartL( ){};

public:
    CMyActiveObject( RMessageQueue* aQueue, TNotifierType aNotifierType, TBool& aIsOpen )
            : CActive(CActive::EPriorityStandard), iCallback(NULL),
            iQueue(aQueue),
            iNotifierType( aNotifierType ),
            iIsOpen( aIsOpen )
    {
        CActiveScheduler::Add(this);
    };

    void ConstructL()
    {
        // No implementation required.
    };

    /**
     * Call the Python notifier callback
     */
    void RunL()
    {
        if ( iStatus == KErrCancel ) return;

        PyObject *result;

        PyEval_RestoreThread(PYTHON_TLS->thread_state);
        result = PyEval_CallObject( iCallback, NULL );
        PyEval_SaveThread();

        // Continue. To stop, return False from the notifier.
        if ( result != Py_False )
        {
            EnableNotifier();
        }
        Py_XDECREF(result);
    };

    void DoCancel(){};

    void EnableNotifier( )
    {
        this->iStatus = KRequestPending;

        switch ( iNotifierType )
        {
        case EDataNotifier:
            iQueue->NotifyDataAvailable( this->iStatus );
            break;

        case ESpaceNotifier:
            iQueue->NotifySpaceAvailable( this->iStatus );
            break;

        default:
            User::Panic( KMsgInvalidNotifier, KErrArgument );
            break;
        }
        this->SetActive();

    }
    void SetListener( PyObject* aCallback )
    {
        Cancel();

        if ( iCallback )
        {
            Cancel();
            Py_XDECREF( iCallback );
        }

        iCallback = aCallback;
        Py_XINCREF(iCallback);

        EnableNotifier();

    };

    PyObject* iCallback;
    RMessageQueue* iQueue;
    TNotifierType iNotifierType;
    /// Reference to Type_MsgQueue::iIsOpen
    TBool& iIsOpen;
};


/// RMsgQueue class wrapper declaration
struct Type_MsgQueue
{
    /// Python type header.
    PyObject_HEAD;
    /// RMsgQueue instance.
    RMessageQueue* iQueue;
    /// Name of the message queue.
    TFullName* iQueueName;
    /// Is the handle open/created.
    TBool iIsOpen;
    /// Notifies when data available in queue.
    CMyActiveObject* iDataAvailable;
    /// Notifies when space available in queue.
    CMyActiveObject* iSpaceAvailable;
};

// Prototypes
static PyObject* MsgQueue_Write( Type_MsgQueue* self, PyObject* args );
static PyObject* MsgQueue_Receive( Type_MsgQueue* self  );
static PyObject* MsgQueue_Open( Type_MsgQueue* self, PyObject* args );
static PyObject* MsgQueue_Create( Type_MsgQueue* self, PyObject* args );
static PyObject* MsgQueue_Close( Type_MsgQueue* self );
static PyObject* MsgQueue_SetSpaceNotifier( Type_MsgQueue* self, PyObject* args );
static PyObject* MsgQueue_SetDataNotifier( Type_MsgQueue* self, PyObject* args );

static const PyMethodDef MsgQueue_methods[] =
{
    {"write",  (PyCFunction)MsgQueue_Write,   METH_VARARGS, "" },
    {"recv",   (PyCFunction)MsgQueue_Receive, METH_NOARGS, "" },
    {"open",   (PyCFunction)MsgQueue_Open,    METH_VARARGS, "" },
    {"create", (PyCFunction)MsgQueue_Create,  METH_VARARGS, "" },
    {"close",  (PyCFunction)MsgQueue_Close,   METH_NOARGS, "" },
    {"set_space_notifier", (PyCFunction)MsgQueue_SetSpaceNotifier,  METH_VARARGS, "" },
    {"set_data_notifier",  (PyCFunction)MsgQueue_SetDataNotifier,   METH_VARARGS, "" },
    {NULL, NULL}
};

/**
  * Destroy old queue object
  */
static void DestroyOldQueue( Type_MsgQueue* self )
{
    // Destroy old
    if ( self->iQueue )
    {
        if ( self->iIsOpen )
        {
            self->iQueue->Close();
            self->iIsOpen = EFalse;
        }
        delete self->iQueue;
        self->iQueue = NULL;
    }
}

/// Close and destroy the Queue
static PyObject* MsgQueue_Close(Type_MsgQueue* self )
{

    DestroyOldQueue( self );

    Py_INCREF(Py_True);
    return Py_True;
}

/**
  * Create a new message queue. Destroys old queue object.
  * Same MsgQueue object cannot be used for creating and opening different queue.
  */
static PyObject* MsgQueue_Create(Type_MsgQueue* self, PyObject* args)
{
    TInt size = 0;

    if (!PyArg_ParseTuple(args, "i", &size))
    {
        return 0;
    }

    DestroyOldQueue( self );

    // Construct the message queue handle
    self->iQueue = new RMessageQueue;
    if ( !self->iQueue )
    {
        return SPyErr_SetFromSymbianOSErr(KErrNoMemory);
    }

    TInt err = self->iQueue->CreateGlobal( *self->iQueueName, size );
    if ( err != KErrNone )
    {
        return SPyErr_SetFromSymbianOSErr(err);
    }

    self->iIsOpen = ETrue;

    Py_INCREF(Py_True);
    return Py_True;
}


/**
  * Sets callbacks.
  * @param self
  * @param args  Contains the callback object.
  * @param aType Determines the type of the notifier.
  */
PyObject *MsgQueue_NotifierCallback(Type_MsgQueue *self, PyObject *args, TNotifierType aType )
{

    // Check if open
    if ( ! self->iIsOpen )
    {
        PyErr_SetString( PyExc_ValueError, KErrMsgQueueNotOpen );
        return NULL;
    }

    PyObject *callback = NULL;
    if (!PyArg_ParseTuple(args, "O", &callback))
    {
        return NULL;
    }

    // If None, only remove and delete the old callback.
    TBool isNone = ( Py_None == callback );
    CMyActiveObject* notifier = NULL;

    if ( !isNone )
    {
        if (!PyCallable_Check(callback))
        {
            PyErr_SetString(PyExc_TypeError, "parameter must be callable or None");
            return NULL;
        }
        // Don't create new if None given.
        notifier = new CMyActiveObject( self->iQueue, aType, self->iIsOpen );
        if ( !notifier )
        {
            return SPyErr_SetFromSymbianOSErr(KErrNoMemory);
        }
    }

    // Remove old notifier and set the instance to correct
    // variable based on notifier type
    switch ( aType )
    {
    case EDataNotifier:
    {
        if ( self->iDataAvailable )
        {
            self->iQueue->CancelDataAvailable();
            delete self->iDataAvailable;
        }
        // Sets to NULL if None given.
        self->iDataAvailable = notifier;
    }
    break;
    case ESpaceNotifier:
    {
        if ( self->iSpaceAvailable )
        {
            self->iQueue->CancelSpaceAvailable();
            delete self->iSpaceAvailable;
        }
        // Sets to NULL if None given.
        self->iSpaceAvailable = notifier;
    }
    break;
    default:
        if ( notifier ) delete notifier;
        User::Panic( KMsgInvalidNotifier, KErrArgument );
        break;
    }

    if ( notifier )
    {
        notifier->SetListener( callback );
    }

    Py_INCREF(Py_True);
    return Py_True;
}

/**
  * Sets callback for available data notifier.
  */
PyObject *MsgQueue_SetDataNotifier(Type_MsgQueue *self, PyObject *args)
{
    return MsgQueue_NotifierCallback (self, args, EDataNotifier );
}

/**
  * Sets callback for available space notifier.
  */
PyObject *MsgQueue_SetSpaceNotifier(Type_MsgQueue *self, PyObject *args)
{
    return MsgQueue_NotifierCallback (self, args, ESpaceNotifier );
}


/**
  * Open existing message queue. Destroys old queue object.
  * Same MsgQueue object cannot be used for creating and opening different queue.
  *
  */
static PyObject* MsgQueue_Open(Type_MsgQueue* self, PyObject* args)
{
    DestroyOldQueue( self );

    // Construct the message queue handle
    self->iQueue = new RMsgQueue<TBuf8<BUF_SIZE> >;
    if ( !self->iQueue )
    {
        return SPyErr_SetFromSymbianOSErr(KErrNoMemory);
    }

    TInt err = self->iQueue->OpenGlobal( *self->iQueueName );
    if ( err != KErrNone )
    {
        return SPyErr_SetFromSymbianOSErr(err);
    }

    self->iIsOpen = ETrue;
    Py_INCREF(Py_True);
    return Py_True;
}

/**
  * Receive data from queue
  */
static PyObject* MsgQueue_Receive( Type_MsgQueue* self )
{
    // Check if open
    if ( ! self->iIsOpen )
    {
        PyErr_SetString( PyExc_ValueError, KErrMsgQueueNotOpen );
        return NULL;
    }

    TBuf8<BUF_SIZE> msg;
    TInt err = KErrNone;

    // Send message and close handle
    err = self->iQueue->Receive(msg);

    // No messages in queue, returns empty string.
    if ( err != KErrUnderflow && err != KErrNone )
    {
        return SPyErr_SetFromSymbianOSErr(err);

    }
    return Py_BuildValue("s#", msg.Ptr(), msg.Length());

}

/**
  * Write data to queue. Maximum size of the message is KMaxName( 128 bytes ).
  */
static PyObject* MsgQueue_Write( Type_MsgQueue* self, PyObject* args)
{
    // Check if open
    if ( ! self->iIsOpen )
    {
        PyErr_SetString( PyExc_ValueError, KErrMsgQueueNotOpen );
        return NULL;
    }

    char* bytes  = NULL;
    TInt length  = 0;

    if (!PyArg_ParseTuple(args, "s#", &bytes, &length
                         ))
    {
        return 0;
    }

    TPtrC8 aMsg((TUint8*)bytes, length );

    TInt err = KErrNone;

    // Send message and close handle
    err = self->iQueue->Send(aMsg);

    if ( err != KErrNone )
    {
        return SPyErr_SetFromSymbianOSErr(err);
    }

    Py_INCREF(Py_True);
    return Py_True;
}

////////////////////////////////////////////////////////////////////////////////
// MsgQueue types definitions
////////////////////////////////////////////////////////////////////////////////


/**MsgQueue python constructor*/
static PyObject* Type_MsgQueue_Construct(PyObject* /*self*/, PyObject * args)
{
    Type_MsgQueue *self = PyObject_New(Type_MsgQueue, &MsgQueueTypeObject);

    if (!self)
    {
        return SPyErr_SetFromSymbianOSErr(KErrNone);
    }

    char* queuename       = NULL;
    TInt queuename_length = 0;

    if (!PyArg_ParseTuple( args, "u#", &queuename, &queuename_length ) )
    {
        PyObject_Del(self);
        return 0;
    }
    self->iQueue     = NULL;
    self->iQueueName = new TFullName;
    if (!self->iQueueName)
    {
        PyObject_Del(self);
        return SPyErr_SetFromSymbianOSErr(KErrNoMemory);
    }

    TPtrC aQueueName((TUint16*)queuename, queuename_length );
    //self->iQueueName.Set( aQueueName );
    //TFullName tmp; tmp.Copy( aQueueName );
    self->iQueueName->Copy( aQueueName );
    self->iIsOpen = EFalse;
    self->iDataAvailable = NULL;
    self->iSpaceAvailable = NULL;

    return (PyObject*)self;
}

/** MsgQueue python destructor */
static void dealloc_MsgQueue(Type_MsgQueue* self)
{
    if ( self->iQueue )
    {

        if ( self->iIsOpen )
        {
            self->iQueue->Close();
            self->iIsOpen = EFalse;
        }

        delete self->iQueue;
        self->iQueue = NULL;
    }

    if ( self->iQueueName)
    {
        delete self->iQueueName;
        self->iQueueName = NULL;
    }

    if ( self->iDataAvailable )
    {
        delete self->iDataAvailable;
    }

    if ( self->iSpaceAvailable )
    {
        delete self->iSpaceAvailable;
    }

    PyObject_Del(self);
}

static PyObject *getattr_MsgQueue(PyObject *self, char *name)
{
    return Py_FindMethod(const_cast<PyMethodDef*>(&MsgQueue_methods[0]), self, name);
}

static const PyTypeObject type_template_MsgQueue =
{
    /*******************************************************/
    PyObject_HEAD_INIT(0)    /* initialize to 0 to ensure Win32 portability */
    0,                 /*ob_size*/
    "_ipc.MsgQueue",            /*tp_name*/
    sizeof(Type_MsgQueue), /*tp_basicsize*/
    0,                 /*tp_itemsize*/
    /* methods */
    (destructor)dealloc_MsgQueue, /*tp_dealloc*/
    0, /*tp_print*/
    (getattrfunc)getattr_MsgQueue, /*tp_getattr*/

    /* implied by ISO C: all zeros thereafter */
};

extern "C"
{

    static const PyMethodDef ipc_methods[] =
    {
        {"MsgQueue", (PyCFunction)Type_MsgQueue_Construct, METH_VARARGS},
        {NULL,              NULL}           /* sentinel */
    };

    DL_EXPORT(void) initipc(void)
    {
        PyObject* m = Py_InitModule("_ipc", (PyMethodDef*)ipc_methods);

        PyObject* d = PyModule_GetDict(m);

        // info flags
        PyDict_SetItemString(d, "BUFFER_MAX_SIZE", PyInt_FromLong( BUF_SIZE ));
    
        // Create MsgQueue class type
        PyTypeObject *MsgQueue_type = PyObject_New(PyTypeObject, &PyType_Type);
        if (!MsgQueue_type)
            return;

        
        
        *MsgQueue_type = type_template_MsgQueue;

        TInt err = SPyAddGlobalString(MsgQueue_TypeString, (PyObject *)MsgQueue_type);
        if (err != KErrNone ) // 0 is success
        {
            PyObject_Del(MsgQueue_type);
            PyErr_SetString(PyExc_Exception, "SPyAddGlobalString failed");
            return;
        }

        MsgQueueTypeObject.ob_type = &PyType_Type;

    }
} /* extern "C" */

#ifndef EKA2
GLDEF_C TInt E32Dll(TDllReason)
{
    return KErrNone;
}
#endif /*EKA2*/
